package com.kevin.algorithm;

import com.kevin.model.Location;
import com.kevin.utils.DistanceUtil;
import com.kevin.utils.MapUtils;
import com.kevin.utils.PointsRep;

import java.util.ArrayList;
import java.util.List;

/**
 * @Description: 凝聚层级聚类：将所有点进行就近分组，然后不停融合中心点，直到满足分组要求
 *
 * @author kevin
 * @version V1.0
 * @date 2019/2/14 14:44
 **/
public class PointGroup implements AlgorithmStrategy{
    private PointsRep pointsRep;

    @Override
    public Location[][] execute(PointsRep pointsRep) {
        this.pointsRep =pointsRep;

        /**
         * 获取聚类后结果
         */
        Location[][] locations = getGroup(pointsRep.getPoints());

        return locations;
    }

    /**
     * 将所有的点进行聚类
     * @return
     */
    private Location[][] getGroup(List<Location> locations){
        /**
         * 将所有点作为独立的堆
         */
        List<List<Location>> groupList = new ArrayList<>();
        for(Location location : locations){
            List<Location> locat = new ArrayList<>();
            locat.add(location);
            groupList.add(locat);
        }

        /**
         * 将所有点作为初始中心点
         */
        List<Location> centerList = new ArrayList<>();
        for(Location location : locations){
            centerList.add(location);
        }

        groupData(centerList,groupList);

        //将堆数据封装成二维数组
        /**
         * 将聚集的数据转换成二维数组
         */
        //计算二维数组第二维长度
        int index = -1;
        for(List<Location> list : groupList){
            if(index == -1){
                index = list.size();
            }else if(index < list.size()){
                index = list.size();
            }
        }
        Location[][] finishGroup = new Location[groupList.size()][index];

        for(int i=0;i<groupList.size();i++){
            for(int j = 0; j < groupList.get(i).size();j++){
                finishGroup[i][j] = groupList.get(i).get(j);
            }
        }

        return finishGroup;
    }

    /**
     * 根据中心将非堆数据放置到堆  方式一
     * @return
     */
    private void groupData(List<Location> centers, List<List<Location>> stackData){
        /**
         * 对比所有未在堆中的数据距离各中心点的距离，选取距离最短的，将点归到该堆
         */
        //第一个堆中心点下标
        int centerIndexA = 0;
        //第二个堆中心点下标
        int centerIndexB = 0;

        //中心点之间最小距离
        double distance = -1;

        for(int i=0;i<stackData.size();i++){
            for(int j=i+1;j<stackData.size();j++){
                double currDistance = Double.valueOf(DistanceUtil.algorithm(centers.get(i).getLon(),centers.get(i).getLat(),centers.get(j).getLon(),centers.get(j).getLat()));

                if(distance == -1){
                    distance = currDistance;
                    centerIndexA = i;
                    centerIndexB = j;
                }else if(currDistance < distance){
                    distance = currDistance;
                    centerIndexA = i;
                    centerIndexB = j;
                }
            }

        }

        if(centerIndexA!=centerIndexB){
            //将未归堆的点归堆
            stackData.get(centerIndexA).addAll(stackData.get(centerIndexB));
            //在未归堆中移除改点
            stackData.remove(centerIndexB);
        }

        /**
         * 重新计算已经归堆的点列表的中心点
         */
        Location currCenter = null;
        //判断堆中是单个点还是多个点，多个点就获取中心
        if(stackData.get(centerIndexA).size()>1){
            currCenter = MapUtils.getCenterPoint(stackData.get(centerIndexA));
        }else{
            currCenter = stackData.get(centerIndexA).get(0);
        }
        centers.set(centerIndexA,currCenter);
        //移除已被合并的堆对应的中心点
        centers.remove(centerIndexB);

        /**
         * 如果还有未归堆点，继续归堆运算
         */
        if(centers.size() > this.pointsRep.getGroup()){
            groupData(centers,stackData);
        }
    }
}
